/*
 This file handles interact with image server.
 The communication api and protocol details are in CPP file of image processing
 server.

 Note that this module only handles IPC with DIP server
*/

package models

import (
	"github.com/astaxie/beego/orm"
	"github.com/dailing/levlog"
	"github.com/garyburd/redigo/redis"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

const (
	DiagnoseStatusCode = iota
	DiagnoseStatusCodeWaitingForProcessing = iota
	DiagnoseStatusCodeFinished = iota
	DiagnoseStatusCodeError = iota
)

type PatientRecord struct {
	ID              int       `orm:"pk;auto;column(id)"`
	PatientName     string    `orm:"column(patient_name)"`
	PatientID       string    `orm:"column(patient_id)"`
	PatientAge      int       `orm:"column(patient_age)"`
	PatientGender   string    `orm:"column(patient_gender)"`
	PatientEyesight string    `orm:"column(eye_sight)"`
	Created         time.Time `orm:"column(created);auto_now_add"`
}

type DiagnoseRecord struct {
	ID            int       `orm:"pk;auto;column(id)"`
	PatientID     int       `orm:"column(patient_id)"`
	ImageOriginal string    `orm:"column(image_original)"`
	Result        string    `orm:"column(result)"`
	Suggest       string    `orm:"column(suggest)"`
	Created       time.Time `orm:"column(created);auto_now_add"`
}

type DiagoniseDetail struct {
	ID            int       `orm:"pk;auto;column(id)"`
	RecordID      int       `orm:"column(record_id)"`
	PatientID     int       `orm:"column(patient_id)"`
	Type          string    `orm:"column(type)"`
	WhichEye      string    `orm:"column(patient_which_eye)"`
	Area          float64   `orm:"column(area)"`
	Count         int       `orm:"column(count)"`
	Percentage    float64   `orm:"column(percentage)"`
	Desp          string    `orm:"column(description)"`
	Image         string    `orm:"column(image)"`
	OriginalImage string    `orm:"column(original_image)"`
	Created       time.Time `orm:"column(created);auto_now_add"`
	State         int       `orm:"column(state)"`
}

type ImageServer struct {
	redisConnPool  *redis.Pool
	redisHost      string
	redisPort      int
	taskQueue      chan (*Jsontype)
	stopSignal     chan (bool)
	finishCallback []func(*Jsontype)
	selectFunc     []func(*Jsontype) bool
}

func NewImageServer() *ImageServer {
	return &ImageServer{
		redisHost: "localhost",
		redisPort: 6379,
		redisConnPool: &redis.Pool{
			MaxIdle:     100,
			IdleTimeout: 240 * time.Second,
			Dial: func() (redis.Conn, error) {
				c, err := redis.Dial("tcp", "127.0.0.1:6379")
				if err != nil {
					return nil, err
				}
				return c, err
			},
		},
		taskQueue:      make(chan (*Jsontype), 0),
		stopSignal:     make(chan (bool), 0),
		finishCallback: make([]func(*Jsontype), 0),
		selectFunc:     make([]func(*Jsontype) bool, 0),
	}
}

func (s *ImageServer) RegisterCallback(callback func(*Jsontype), selectf func(*Jsontype) bool) *ImageServer {
	s.finishCallback = append(s.finishCallback, callback)
	s.selectFunc = append(s.selectFunc, selectf)
	return s
}

func (s *ImageServer) Start() error {
	// add tasks to queue
	go func() {
		levlog.Trace("Task routine start")
		conn, err := s.redisConnPool.Dial()
		if err != nil {
			levlog.E(err)
			return
		}
		if conn != nil {
			defer conn.Close()
		}
		for {
			select {
			case task := <-s.taskQueue:
				levlog.Trace("Adding task: ", task.ReadObject("mata_data").ReadInt("task_id"))
				_, err := conn.Do("LPUSH", "task_queue", task.ToString())
				levlog.E(err)
			case <-s.stopSignal:
				levlog.Trace("Task routine exit")
			}
		}
		return
	}()

	// receive finished tasks, this task should receive a callback for async process
	// and should return as fast as possiable
	go func() {
		if s.finishCallback == nil {
			levlog.Fatal("Error, No call back for finish queue")
		}
		levlog.Trace("reveive task tarted")
		conn, err := s.redisConnPool.Dial()
		if conn != nil {
			defer conn.Close()
		}
		if err != nil {
			levlog.E(err)
			return
		}
		for {
			strs, err := redis.Strings(conn.Do("BRPOP", "finish_queue", 2))
			if err == redis.ErrNil {
				select {
				case <-s.stopSignal:
					levlog.Trace("Stoping task queue")
					break
				default:
					continue
				}
			}
			levlog.E(err)
			if len(strs) != 2 || strs[0] != "finish_queue" {
				levlog.Error("Response from DB is incorrect")
				continue
			}
			//levlog.Trace("received ", strs[1])
			j := ParseJson(strs[1])
			if j == nil {
				levlog.Fatal("Error")
			}
			for index, callback := range s.finishCallback {
				if s.finishCallback[index] == nil || s.selectFunc[index] == nil {
					levlog.Error("Empty callback")
					continue
				}
				sel := s.selectFunc[index]
				if sel(&j) {
					callback(&j)
				}
			}
		}
	}()
	return nil
}

func (s *ImageServer) Stop() {
	close(s.stopSignal)
}

func (s *ImageServer) AddDiagnose(j Jsontype) error {
	checkKey := func(_j Jsontype, s string) {
		if !_j.HasKey(s) {
			levlog.Error("Json file has no key:", s)
		}
	}
	checkKey(j, "mata_data")
	checkKey(j, "files")
	checkKey(j.ReadObject("mata_data"), "task_id")
	checkKey(j.ReadObject("files"), "original_image")
	s.taskQueue <- &j
	return nil
}

func SysInit() error {
	err := orm.RegisterDriver("mysql", orm.DRMySQL)
	if err != nil {
		levlog.E(err)
		return err
	}
	err = orm.RegisterDataBase("default", "mysql", "root:1@tcp(127.0.0.1:3306)/DeepDRSys?charset=utf8")
	if err != nil {
		levlog.E(err)
		return err
	}
	orm.RegisterModel(new(PatientRecord))
	orm.RegisterModel(new(DiagnoseRecord))
	orm.RegisterModel(new(DiagoniseDetail))
	return createTable()
}

func createTable() error {
	levlog.Trace("Creating tables")
	name := "default"
	force := false
	verbose := true
	err := orm.RunSyncdb(name, force, verbose)
	if err != nil {
		levlog.E(err)
	}
	return err
}
